Waymo ยท Software Engineer, Fleet Monitoring

Study Guide [Front-End]

Reference and drills for the live JavaScript/TypeScript round โ€” UI implementation, fleet-data manipulation, and core patterns. LeetCode-style algorithm problems are handled separately in Python.
Per the official TPS guide: live coding in JavaScript/TypeScript, covering UI implementation (any framework or vanilla JS/TS), arrays/strings/objects/sets/maps, simple graph & tree traversal (BFS/DFS), recursion, sorting/filtering logic, and time/space complexity discussion.
Waymo TPS โ€” What Actually Matters

High priority (70% of your time)

  • Fleet data manipulation โ€” filter, sort, reduce, Map
  • UI component builds โ€” filterable list, debounced search, tree view
  • DOM manipulation โ€” createElement, addEventListener, render from data
  • Light BFS/DFS โ€” level order, grid traversal, simple tree recursion
  • Narrating Big-O out loud for every solution

Low priority โ€” skip for this role

  • Hard graph problems (clone graph, topological sort)
  • Backtracking โ€” subsets, permutations, combination sum
  • GFE polyfills track (debounce/curry/deep clone) โ€” useful but not Waymo-specific
  • Heap / priority queue
  • Dynamic programming

Example problem they could give

"You have an array of vehicle objects: { id, status, stoppedSeconds, zoneId }. Build a UI that renders the list, lets the user filter by status via a dropdown, and sorts by stoppedSeconds descending. Add a debounced text input that filters by id."

This hits every bullet on the TPS guide: arrays/maps, sorting/filtering, UI implementation, and you'd state O(n log n) for the sort. Do this build before the interview.

Before the call

Confirm with the recruiter

Ask directly: which round tests UI-building vs. DSA-in-JS (or both, per the guide), what coding environment they use (CoderPad, CodeSandbox, shared doc), and whether vanilla JS is genuinely fine or if they lean toward seeing a framework.

CoderPad React pad โ€” two quirks to expect (Waymo uses CoderPad):

1. No code formatter for JSX/TSX. "Format Document" throws "no formatter for typescriptreact/javascriptreact", and renaming .tsx โ†’ .jsx โ†’ .js does NOT fix it โ€” any file containing JSX is a React language mode with no Prettier wired in. Don't burn interview time chasing it. Keep prettier.io/playground open in a tab as a paste-in/paste-back fallback, or just indent cleanly as you type (Monaco auto-indents on newlines and braces). Interviewers don't dock you for imperfect spacing.

2. Type squiggles don't block running. The pad runs via Vite/esbuild, which STRIPS types without type-checking, so red TS squiggles are cosmetic โ€” the preview still renders. To write plain untyped JS in a .tsx file with zero noise, put // @ts-nocheck as the first line of the file. Alternatives: use a .jsx file, or add : any to the few flagged params. TS also infers most types, so only untyped function params (e.g. destructured props) tend to complain.
Reference Cheatsheet

JavaScript Patterns Cheatsheet

Key syntax and patterns for every topic in the TPS guide โ€” memorize these so you never pause mid-interview to look up a method name.
Core Data Structures
// Arrays
const arr = [1, 2, 3];
arr.push(4);              // add to end โ€” O(1)
arr.pop();                // remove from end โ€” O(1)
arr.shift();              // remove from front โ€” O(n) โš ๏ธ not O(1) like Python deque
arr.unshift(0);           // add to front โ€” O(n)
arr.slice(1, 3);          // [arr[1], arr[2]] โ€” non-mutating, end-exclusive
arr.splice(1, 2);         // removes 2 elements starting at index 1 โ€” mutates!
arr.length;
arr[arr.length - 1];      // last element (no arr[-1] in JS)
[...arr].reverse();       // reversed copy; arr.reverse() mutates in-place

// Sets โ€” O(1) average membership
const s = new Set([1, 2, 3]);
s.add(4);
s.delete(2);
s.has(3);                 // true
s.size;                   // 3 (not .length)
[...s];                   // convert to array
const unique = [...new Set(arr)];

// Map โ€” ordered, any key type, better than plain objects for algo work
const mp = new Map();
mp.set('a', 1);
mp.get('a');              // 1
mp.has('b');              // false
mp.delete('a');
mp.size;
mp.get('missing');        // undefined โ€” no KeyError
// safe default: mp.get(key) ?? 0

// Plain objects โ€” fine for string/int keys in interviews
const obj = { a: 1, b: 2 };
obj['c'] = 3;
delete obj['a'];
'b' in obj;               // true
Object.keys(obj);         // ['b', 'c']
Object.values(obj);       // [2, 3]
Object.entries(obj);      // [['b',2], ['c',3]]
Iteration Patterns
// for...of โ€” iterates VALUES (use for arrays, sets, maps)
for (const x of arr) { ... }
for (const x of s) { ... }       // Set values
for (const [k, v] of mp) { ... } // Map entries

// for...in โ€” iterates KEYS (avoid for arrays โ€” iterates indices as strings)
for (const key in obj) { ... }   // use only for plain objects

// Index + value (equiv of enumerate)
for (let i = 0; i < arr.length; i++) { ... }
arr.forEach((val, i) => { ... });

// Object iteration
for (const [k, v] of Object.entries(obj)) { ... }

// Map iteration
for (const [k, v] of mp.entries()) { ... }
mp.forEach((val, key) => { ... });
Functional Methods
These aren't a primary TPS topic, but you'll use them constantly as tools. Using .filter().map() over a manual for-loop signals JS fluency. Waymo context: expect questions framed around vehicle/fleet data (filtering by status, grouping by type, transforming telemetry) where these are the natural fit.

Explicit patterns โ€” memorise these

// โ”€โ”€ map(callback) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// callback receives: (currentItem, index, originalArray)
// must RETURN the new value for each item
// result: NEW array, same length as input
const result = array.map((currentItem, index) => {
  return /* transformed version of currentItem */;
});

// one-line arrow: implicit return (no curly braces needed)
const ids = vehicles.map(vehicle => vehicle.id);


// โ”€โ”€ filter(callback) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// callback receives: (currentItem, index, originalArray)
// must RETURN true (keep) or false (discard)
// result: NEW array, same length or shorter
const result = array.filter((currentItem, index) => {
  return /* true to keep, false to drop */;
});

// one-line arrow: implicit return
const stopped = vehicles.filter(vehicle => vehicle.stationarySeconds > 300);


// โ”€โ”€ reduce(callback, initialValue) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// callback receives: (accumulator, currentItem, index, originalArray)
// must RETURN the accumulator โ€” this becomes the input for the next step
// result: ONE value (number, string, object, Map, array โ€” anything)
//
// Two levels of arguments:
//   outer: reduce(callbackFn,  initialValue)
//                 โ†‘ the whole    โ†‘ starting
//                   function       value of accumulator
//   inner: (accumulator, currentItem) โ€” parameters INSIDE the callback
const result = array.reduce((accumulator, currentItem) => {
  // update accumulator using currentItem
  return accumulator;  // ALWAYS return it, or next step gets undefined
}, startingValue);

// one-line: implicit return works when it fits
const sum = arr.reduce((accumulator, item) => accumulator + item, 0);

// reduce is great for building a Map โ€” Map is technically "one value"
const byId = vehicles.reduce((accumulator, vehicle) => {
  accumulator.set(vehicle.id, vehicle);
  return accumulator;
}, new Map());

// reduce for frequency count โ€” object is also "one value"
const freq = arr.reduce((accumulator, item) => {
  accumulator[item] = (accumulator[item] ?? 0) + 1;
  return accumulator;
}, {});

Full examples

// โ”€โ”€ map โ”€โ”€ transforms every element, returns a NEW array (same length)
const doubled  = arr.map(x => x * 2);
const lengths  = words.map(w => w.length);
const ids      = vehicles.map(v => v.id);

// โ”€โ”€ filter โ”€โ”€ keeps elements where the test returns true, returns NEW array
const evens    = arr.filter(x => x % 2 === 0);
const stopped  = vehicles.filter(v => v.stationarySeconds > 300);
// does NOT mutate the original array

// โ”€โ”€ reduce โ”€โ”€ folds the array into a single value (number, object, Map, etc.)
const sum  = arr.reduce((accumulator, x) => accumulator + x, 0);
const freq = arr.reduce((accumulator, x) => {
  accumulator[x] = (accumulator[x] ?? 0) + 1;
  return accumulator;
}, {});
// build a Map with reduce โ€” Map is one object, so reduce is the right tool
const byId = vehicles.reduce((accumulator, v) => {
  accumulator.set(v.id, v);
  return accumulator;
}, new Map());

// โ”€โ”€ chaining โ€” the real power; each method returns a new array
// "IDs of vehicles stopped > 5 min, sorted longest-stopped first"
const result = vehicles
  .filter(v => v.stationarySeconds > 300)
  .sort((a, b) => b.stationarySeconds - a.stationarySeconds)
  .map(v => v.id);

// โ”€โ”€ forEach โ”€โ”€ like map but returns undefined; use when you only want side effects
arr.forEach((val, i) => console.log(i, val));
// don't chain off forEach โ€” it returns nothing

// โ”€โ”€ find / findIndex โ”€โ”€ return the first match (or undefined / -1)
arr.find(x => x > 3);            // first value matching, or undefined
arr.findIndex(x => x > 3);       // first index matching, or -1
vehicles.find(v => v.id === 42); // lookup by field

// โ”€โ”€ some / every โ”€โ”€ short-circuit boolean checks
arr.some(x => x > 10);    // true if ANY element matches (like Python any())
arr.every(x => x > 0);    // true if ALL elements match (like Python all())
vehicles.some(v => v.faultCode !== null);  // any vehicle has a fault?

// โ”€โ”€ flat / flatMap โ”€โ”€ flatten nested arrays
[[1,2],[3,4]].flat();               // [1,2,3,4] โ€” one level
[[[1]],[[2]]].flat(2);              // [1,2] โ€” two levels (or Infinity)
arr.flatMap(x => [x, x * 2]);      // map then flatten one level โ€” more efficient than .map().flat()

// โ”€โ”€ Array.from โ”€โ”€ create array from iterables or with a fill function
Array.from('hello');               // ['h','e','l','l','o']
Array.from(new Set([1,2,3]));      // [1,2,3]
Array.from({length: 5}, (_, i) => i);  // [0,1,2,3,4] โ€” like range(5)
Array.from({length: m}, () => new Array(n).fill(0)); // 2D grid init
Algorithm Templates
// Two pointers
let l = 0, r = arr.length - 1;
while (l < r) { ... l++; r--; }

// Sliding window
const window = new Map();
let l = 0;
for (let r = 0; r < s.length; r++) {
  window.set(s[r], (window.get(s[r]) ?? 0) + 1);
  while (isInvalid(window)) {
    window.set(s[l], window.get(s[l]) - 1);
    if (window.get(s[l]) === 0) window.delete(s[l]);
    l++;
  }
}

// BFS โ€” graph (array-as-queue; .shift() is O(n) but fine for interviews)
function bfs(graph, start) {
  const queue = [start];
  const visited = new Set([start]);
  while (queue.length) {
    const node = queue.shift();
    for (const nei of (graph.get(node) ?? [])) {
      if (!visited.has(nei)) {
        visited.add(nei);
        queue.push(nei);
      }
    }
  }
}

// BFS โ€” 2D grid
function bfsGrid(grid, startR, startC) {
  const rows = grid.length, cols = grid[0].length;
  const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
  const inBounds = (r, c) => r >= 0 && r < rows && c >= 0 && c < cols;
  const queue = [[startR, startC]];
  const visited = new Set([`${startR},${startC}`]);
  while (queue.length) {
    const [r, c] = queue.shift();
    for (const [dr, dc] of dirs) {
      const nr = r + dr, nc = c + dc;
      const key = `${nr},${nc}`;
      if (inBounds(nr, nc) && !visited.has(key)) {
        visited.add(key);
        queue.push([nr, nc]);
      }
    }
  }
}

// DFS โ€” iterative
const stack = [start];
const visited = new Set([start]);
while (stack.length) {
  const node = stack.pop();
  for (const nei of graph.get(node) ?? []) {
    if (!visited.has(nei)) {
      visited.add(nei);
      stack.push(nei);
    }
  }
}

// DFS โ€” recursive (tree)
function dfs(node) {
  if (!node) return 0;
  const left = dfs(node.left);
  const right = dfs(node.right);
  return 1 + Math.max(left, right);
}

// Binary search
function binarySearch(arr, target) {
  let lo = 0, hi = arr.length - 1;
  while (lo <= hi) {
    const mid = Math.floor((lo + hi) / 2);
    if (arr[mid] === target) return mid;
    else if (arr[mid] < target) lo = mid + 1;
    else hi = mid - 1;
  }
  return -1;
}
JS Gotchas vs. Python
These are the most common Python-to-JS traps. Know them cold โ€” interviewers notice when you accidentally use == or forget to pass a sort comparator.
// 1. == vs === โ€” ALWAYS use ===
0 == false   // true  โ€” type coercion
0 === false  // false โ€” strict, no coercion
'' == false  // true
null == undefined  // true
null === undefined // false
// Rule: use === everywhere; the only exception is null checks:
if (x == null) { ... } // catches both null and undefined โ€” intentional shortcut

// 2. Reference equality โ€” objects and arrays compare by REFERENCE
[1,2,3] === [1,2,3]  // false โ€” different objects in memory
{a:1} === {a:1}      // false
// Deep equality โ€” no built-in; options:
JSON.stringify(a) === JSON.stringify(b)  // works for simple objects, fragile for undefined/functions
// In interviews: manually compare fields, or note you'd use _.isEqual(a, b)

// 3. Sort without comparator is LEXICOGRAPHIC โ€” even for numbers
[10, 9, 2].sort()                 // [10, 2, 9] โ€” "10" < "2" alphabetically
[10, 9, 2].sort((a, b) => a - b) // [2, 9, 10]

// 4. Falsy values โ€” all of these are falsy in JS
false, 0, -0, 0n, "", '', ``, null, undefined, NaN
// Note: empty array [] and empty object {} are TRUTHY
if ([]) console.log('truthy')   // prints โ€” unlike Python where [] is falsy

// 5. NaN is not equal to itself
NaN === NaN  // false
Number.isNaN(NaN)  // true โ€” use this to check

// 6. null vs undefined
// null   โ€” intentional absence (you set it)
// undefined โ€” variable declared but not assigned, or missing property
typeof null       // "object" โ€” famous JS bug
typeof undefined  // "undefined"
Array.isArray([]) // true โ€” use this, not typeof (which returns "object" for arrays)

// 7. Type coercion with +
"5" + 3   // "53" โ€” string concatenation wins
"5" - 3   // 2   โ€” arithmetic (- forces numeric)
+"5"      // 5   โ€” unary + converts to number
Number("5") // 5
parseInt("5px") // 5  โ€” stops at non-numeric

// 8. var vs let vs const
// var   โ€” function-scoped, hoisted, can redeclare โ€” avoid
// let   โ€” block-scoped, can reassign
// const โ€” block-scoped, cannot reassign binding (but object contents are mutable)
const arr = [1, 2, 3];
arr.push(4);    // ok โ€” mutating contents
arr = [5];      // TypeError โ€” can't reassign the binding

// 9. Array .shift() is O(n) โ€” use index pointer for performance-critical code
// For interview BFS, .shift() is fine; just mention this tradeoff if asked
let head = 0;
while (head < queue.length) {
  const node = queue[head++]; // O(1) dequeue alternative
}

// 10. for...in on arrays โ€” iterates string indices, not values; avoid
for (const i in [10, 20]) console.log(i);  // "0", "1" โ€” strings!
for (const x of [10, 20]) console.log(x);  // 10, 20

Quick signal โ†’ tool lookup:

"top K" / frequency countMap + sort, or max-heap (implement with sorted array)
BFS / shortest patharray queue with .shift() or index pointer
membership checkSet (O(1)) โ€” not array .includes() (O(n))
key-value storeMap โ€” not plain object (avoids prototype key collisions)
graph adjacency / groupingMap<node, node[]>
2D grid coordinatesencode as `${r},${c}` string key in a Set
sort by custom rule.sort((a, b) => ...)
Reference Drills

JavaScript Syntax Drills

One pattern per section โ€” a code snippet to memorize for the live JS/TS round. Write these in JavaScript, not Python.
Array Methods
push / pop / slice / spread
const arr = [3, 1, 4, 1, 5];
arr.push(9);                  // [3,1,4,1,5,9]
arr.pop();                    // 9 (returns removed element)
arr.slice(1, 3);              // [1, 4] โ€” non-mutating
const copy = [...arr];        // shallow copy
const merged = [...a, ...b];  // concat without mutation

// No Python arr[-1] โ€” use arr[arr.length - 1] or arr.at(-1)
arr.at(-1);                   // last element (modern JS)
Map and Set
Map โ€” frequency / adjacency list
// Frequency count (Python Counter equivalent)
const freq = new Map();
for (const c of s) {
  freq.set(c, (freq.get(c) ?? 0) + 1);
}

// Adjacency list (Python defaultdict(list) equivalent)
const graph = new Map();
for (const [u, v] of edges) {
  if (!graph.has(u)) graph.set(u, []);
  if (!graph.has(v)) graph.set(v, []);
  graph.get(u).push(v);
  graph.get(v).push(u);
}
Set โ€” dedup / visited tracking
// Dedup
const unique = [...new Set(arr)];

// Visited set for BFS/DFS
const visited = new Set();
visited.add(node);
if (!visited.has(node)) { ... }

// 2D grid visited โ€” encode coordinates as string
const seen = new Set();
seen.add(`${r},${c}`);
if (seen.has(`${r},${c}`)) { ... }
Sorting with Comparators
.sort((a, b) => ...) โ€” always pass a comparator
// Numbers ascending / descending
nums.sort((a, b) => a - b);
nums.sort((a, b) => b - a);

// Sort objects by property
intervals.sort((a, b) => a[0] - b[0]);  // by start
people.sort((a, b) => a.age - b.age);

// Multi-key sort
arr.sort((a, b) => a.len - b.len || a.name.localeCompare(b.name));

// Non-mutating sort
const sorted = [...arr].sort((a, b) => a - b);
Functional Programming
map / filter / reduce โ€” chaining pattern

map transforms every element into something else (same length). filter removes elements that don't pass a test (shorter or equal length). reduce collapses the array into one value โ€” a number, object, Map, or anything else. Chain them: filter first (smaller array), then sort, then map.

// Waymo-style example:
// "Return the IDs of vehicles stopped over 5 min, sorted longest-stopped first."
const result = vehicles
  .filter(v => v.stationarySeconds > 300)          // keep stopped vehicles
  .sort((a, b) => b.stationarySeconds - a.stationarySeconds) // longest first
  .map(v => v.id);                                  // extract IDs only

// Equivalent for-loop version โ€” valid, just less idiomatic in JS:
const result2 = [];
for (const v of vehicles) {
  if (v.stationarySeconds > 300) result2.push(v);
}
result2.sort((a, b) => b.stationarySeconds - a.stationarySeconds);
const ids = result2.map(v => v.id);

// reduce to build a frequency map
const freq = arr.reduce((acc, x) => {
  acc[x] = (acc[x] ?? 0) + 1;
  return acc;
}, {});

// some / every for early-exit boolean checks
vehicles.some(v => v.faultCode !== null);  // any fault?
vehicles.every(v => v.batteryPct > 20);   // all above threshold?
BFS and DFS
BFS โ€” level order traversal
// Binary tree level order
function levelOrder(root) {
  if (!root) return [];
  const result = [];
  const queue = [root];
  while (queue.length) {
    const levelSize = queue.length;
    const level = [];
    for (let i = 0; i < levelSize; i++) {
      const node = queue.shift();
      level.push(node.val);
      if (node.left)  queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
    result.push(level);
  }
  return result;
}
DFS โ€” recursive tree
// Max depth (recursive DFS)
function maxDepth(root) {
  if (!root) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

// DFS on 2D grid (recursive)
function dfs(grid, r, c, visited) {
  const rows = grid.length, cols = grid[0].length;
  if (r < 0 || r >= rows || c < 0 || c >= cols) return;
  const key = `${r},${c}`;
  if (visited.has(key) || grid[r][c] === '0') return;
  visited.add(key);
  dfs(grid, r+1, c, visited);
  dfs(grid, r-1, c, visited);
  dfs(grid, r, c+1, visited);
  dfs(grid, r, c-1, visited);
}
Recursion low priority for Waymo
Backtracking template
// Subsets / combinations (backtracking)
function subsets(nums) {
  const result = [];
  function backtrack(start, current) {
    result.push([...current]);   // spread to copy โ€” don't push reference!
    for (let i = start; i < nums.length; i++) {
      current.push(nums[i]);
      backtrack(i + 1, current);
      current.pop();             // undo choice
    }
  }
  backtrack(0, []);
  return result;
}
GFE 1-Week Polyfills Track low priority for Waymo
From the GFE Front-End Interview Playbook 1-week plan. Good JS fundamentals practice but not Waymo-specific โ€” do the UI builds and data manipulation drills first. Come back here if you have time.
Day 1โ€“2 โ€” Debounce & Throttle

Understand leading/trailing edges and how this is preserved. GFE has a Debounce 2 that adds .cancel() and .flush() โ€” do that after the basics.

// Debounce โ€” delay until user stops
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Throttle โ€” fire at most once per interval
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}
Day 3 โ€” Promise.all & Promise.any

Be confident with resolve/reject semantics. Promise.all rejects on first failure; Promise.any resolves on first success.

// Promise.all โ€” rejects if ANY rejects
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let remaining = promises.length;
    if (remaining === 0) return resolve([]);
    promises.forEach((p, i) => {
      Promise.resolve(p).then(val => {
        results[i] = val;
        if (--remaining === 0) resolve(results);
      }).catch(reject);
    });
  });
}

// Promise.any โ€” resolves if ANY resolves
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    const errors = [];
    let remaining = promises.length;
    if (remaining === 0) return reject(new AggregateError([], 'All promises were rejected'));
    promises.forEach((p, i) => {
      Promise.resolve(p).then(resolve).catch(err => {
        errors[i] = err;
        if (--remaining === 0) reject(new AggregateError(errors, 'All promises were rejected'));
      });
    });
  });
}
Day 4 โ€” Curry & Flatten

Both are recursion patterns. Curry uses fn.length to know when to stop collecting args. Flatten needs to handle arbitrary depth.

// Curry โ€” collect args until fn.length is satisfied
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) return fn(...args);
    return (...more) => curried(...args, ...more);
  };
}

// Flatten โ€” recursive with depth
function flatten(arr, depth = Infinity) {
  return arr.reduce((acc, val) => {
    if (Array.isArray(val) && depth > 0) {
      acc.push(...flatten(val, depth - 1));
    } else {
      acc.push(val);
    }
    return acc;
  }, []);
}
Day 5 โ€” Deep Clone

The hard part is handling circular references, Date, Map, and Set. JSON.parse(JSON.stringify(x)) is not a valid answer โ€” it drops functions, undefined, and Date objects.

function deepClone(val, seen = new Map()) {
  if (val === null || typeof val !== 'object') return val;
  if (seen.has(val)) return seen.get(val);   // circular ref guard
  if (val instanceof Date) return new Date(val);
  if (val instanceof Set) {
    const s = new Set();
    seen.set(val, s);
    val.forEach(v => s.add(deepClone(v, seen)));
    return s;
  }
  if (val instanceof Map) {
    const m = new Map();
    seen.set(val, m);
    val.forEach((v, k) => m.set(deepClone(k, seen), deepClone(v, seen)));
    return m;
  }
  const clone = Array.isArray(val) ? [] : {};
  seen.set(val, clone);
  for (const key of Object.keys(val)) {
    clone[key] = deepClone(val[key], seen);
  }
  return clone;
}
Day 6 โ€” EventEmitter

OOP + method chaining. Implement on, off, emit. Common follow-up: once (fires exactly once, then auto-removes).

class EventEmitter {
  constructor() { this._events = {}; }

  on(event, listener) {
    (this._events[event] ??= []).push(listener);
    return this;   // enables chaining
  }

  off(event, listener) {
    if (!this._events[event]) return this;
    this._events[event] = this._events[event].filter(l => l !== listener);
    return this;
  }

  emit(event, ...args) {
    (this._events[event] ?? []).forEach(l => l(...args));
    return this;
  }

  once(event, listener) {
    const wrapper = (...args) => { listener(...args); this.off(event, wrapper); };
    return this.on(event, wrapper);
  }
}
Day 7 โ€” Timed Retry

Pick your weakest problem from Days 1โ€“6. Set a 35-minute timer. Solve it without looking at the solution. Then review carefully.

UI Patterns โ€” Vanilla JS
DOM manipulation + event handling
// Select elements
document.getElementById('my-id');
document.querySelector('.my-class');         // first match
document.querySelectorAll('li');             // NodeList (use [...] to get array)

// Create and insert
const li = document.createElement('li');
li.textContent = 'Item';
li.classList.add('active');
ul.appendChild(li);
ul.insertBefore(li, ul.firstChild);         // prepend

// Remove
el.remove();
parent.removeChild(child);

// Event listeners
btn.addEventListener('click', (e) => { ... });
input.addEventListener('input', handler);   // fires on every keystroke
input.addEventListener('change', handler);  // fires on blur/enter

// Debounce โ€” delay execution until user pauses typing
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
const debouncedFilter = debounce(filterList, 300);
input.addEventListener('input', debouncedFilter);

// Render a list from data
function renderList(items) {
  ul.innerHTML = '';   // clear existing
  for (const item of items) {
    const li = document.createElement('li');
    li.textContent = item.name;
    ul.appendChild(li);
  }
}

// Toggle expand/collapse
btn.addEventListener('click', () => {
  const isOpen = content.style.display !== 'none';
  content.style.display = isOpen ? 'none' : 'block';
  btn.textContent = isOpen ? 'โ–ถ' : 'โ–ผ';
});
  • Build: filterable list with debounced search
  • Build: expandable tree view (recursive render)
  • Build: counter with stack-based undo history
Prepared for Sophia ยท Waymo, Software Engineer, Fleet Monitoring